"""
随机样本梯度更新
优化权重更新方式，使用 numpy 加速计算
"""

import random
import pandas as pd
import numpy as np
import os
import time

print("选择工作目录")
if os.getcwd().split("\\")[-1] == "project":
    os.chdir(r"03.Logistic-regression")

# 定义每列的映射关系
column_mappings = {
    "menopause": {"premeno": 0, "ge40": 1},
    "node-caps": {"no": 0, "yes": 1},
    "deg-malig": {"2": 0, "3": 1},
    "breast": {"left": 0, "right": 1},
    "irradiat": {"no": 0, "yes": 1},
    "Class": {"no-recurrence-events": 0, "recurrence-events": 1},
}


def load_data(file_path):
    df = pd.read_excel(file_path, dtype=str)
    # 加载映射关系
    for column, mapping in column_mappings.items():
        df[column] = df[column].map(mapping)
    return df


# 计算 sigmod 导数
def derivative_sigmod(x):
    return x * (1 - x)


# sigmod 函数
def sigmod(x):
    return 1 / (1 + np.exp(-x))


# 计算线性函数值
def linear_value(weights: np.ndarray, bias: float, x: np.ndarray):
    return np.dot(weights, x) + bias


# 测试集查看训练精度
def test(df: pd.DataFrame, weights: np.ndarray, bias: float):
    # 读取每个样本
    correct = 0
    for _, row in df.iterrows():
        # 获取样本的特征值
        x = row.iloc[:-1]
        # 获取样本的标签值
        y = row.iloc[-1]

        # 计算预测值
        y_hat = sigmod(linear_value(weights, bias, x))
        if (y == 0 and y_hat < 0.5) or (y == 1 and y_hat >= 0.5):
            correct += 1
    print(f"预测正确：{correct}，总样本数：{len(df)}")
    return correct / len(df)


def fit(df: pd.DataFrame):
    # 初始化权重和偏置
    # 5 个权重，初始化为 0~1 之间的随机数
    weights = np.random.rand(5)
    # 偏置，初始化为 0
    bias = 0.0
    print("初始权重：", weights)
    print("初始偏置：", bias)
    # 学习率
    alpha = 0.01

    # 训练 180 轮
    for _ in range(180):
        # 读取每个样本
        for _, row in df.iterrows():
            # 获取样本的特征值
            x = row.iloc[:-1].to_numpy()
            # 获取样本的标签值
            y = row.iloc[-1]

            # 正向传播
            # 计算预测值
            y_hat = sigmod(linear_value(weights, bias, x))

            # 反向传播，更新参数
            # 计算 \hat {y}（线性函数值）
            z = linear_value(weights, bias, x)
            # 计算 \frac{\part \hat{y}}{\part z}（sigmod 的导数）
            h = sigmod(z) * (1 - sigmod(z))
            # 计算 \frac{\part J}{\part \hat{y}}
            s = -(y / y_hat) - (1 - y) / (y_hat - 1)
            # 更新每个权重
            weights = weights - alpha * s * h * x
            # 更新偏置
            bias = bias - alpha * s * h

    print("最终权重：", weights)
    print("最终偏置：", bias)
    return weights, bias


if __name__ == "__main__":
    random.seed(1)
    np.random.seed(1)

    print("读取数据，加载映射关系")
    df_train = load_data("breast-cancer-train.xlsx")
    df_test = load_data("breast-cancer-test.xlsx")
    print("加载数据集预览：")
    print(df_train.head())

    start = time.time()
    weights, bias = fit(df_train)
    print("训练时间：", time.time() - start)

    accuracy = test(df_test, weights, bias)
    print(f"测试集精度：{accuracy}")
